import javax.swing.text.Keymap;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


class Import
{
    Id alias;
    ArrayList<String> pathSpec;
}


class ScopeObject
{
    enum KIND{Func, Type, Const, Var, Pkg};
    KIND kind;
    String name;
    Object field;
    Object value;

    ScopeObject()
    {

    }

    ScopeObject(Object field, KIND kind, String name)
    {
        this.field = field;
        this.kind = kind;
        this.name = name;
    }
}

class Scope
{
    Scope parent;
    HashMap<String, ScopeObject> objects;

    Scope()
    {
        parent = null;
        objects = new HashMap<String, ScopeObject>();
    }

    Scope(Scope parent)
    {
        this.parent = parent;
        objects = new HashMap<String, ScopeObject>();
    }

    public ScopeObject find(String name)
    {
        if(objects.containsKey(name))
            return objects.get(name);
        return null;
    }

    public void insert(ScopeObject object)
    {
        objects.put(object.name, object);
    }
}

public class AstTree
{
    public List<Import> imports;
    public List<Id> ids;
    public String path;
    public String packageName;
    public Scope rootScope;
    public Scope currentScope;
    public List<Id> unresolve;
    private int embeddedId = 0;
    public List<Decl> decls;

    public AstTree()
    {
        ids = new ArrayList<Id>();
        imports = new ArrayList<Import>();
        rootScope = new Scope();
        currentScope = rootScope;
        unresolve = new ArrayList<Id>();
        decls = new ArrayList<>();
    }
    public AstTree(String name)
    {
        ids = new ArrayList<Id>();
        imports = new ArrayList<Import>();
        Id id = new Id();
        id.name = name;
        ids.add(id);
        rootScope = new Scope();
        currentScope = rootScope;
        unresolve = new ArrayList<Id>();
        decls = new ArrayList<>();
    }

    public void setPath(String path)
    {
        this.path = path;
    }

    public void intoScope()
    {
        Scope scope = new Scope(currentScope);
        currentScope = scope;
    }

    public void outScope()
    {
        if(currentScope.parent == null)
            return;
        currentScope = currentScope.parent;
    }

    public List<String> getImportPath()
    {
        List<String> ans = new ArrayList<>();
        for(Import _import : imports)
        {
            for(String path : _import.pathSpec)
                ans.add(path.substring(1, path.length()-1));
        }
        return ans;
    }

    public ScopeObject resolve(Expr id)
    {
        if(id.name.equals("_"))
        {
            return null;
        }

        for(Scope i=currentScope; i!=null; i=i.parent)
        {
            ScopeObject object = i.find(id.name);
            if(object != null)
            {
                return object;
            }
        }

        unresolve.add((Id)id);
        return null;
    }

    public void declareField(Object field, ScopeObject.KIND kind, Id name)
    {
        ScopeObject scopeObject = new ScopeObject(field, kind, name.name);
        name.object = scopeObject;
        if(!name.name.equals("_"))
        {
            currentScope.insert(scopeObject);
        }
    }

    public void declareValue(ValueDecl valueDecl, ScopeObject.KIND kind)
    {
        for(Id id : valueDecl.names)
        {
            ScopeObject scopeObject = new ScopeObject(valueDecl, kind, id.name);
            id.object = scopeObject;
            currentScope.insert(scopeObject);
        }
    }

    public void declareType(TypeDecl typeDecl, ScopeObject.KIND kind)
    {
        ScopeObject scopeObject = new ScopeObject(typeDecl, kind, typeDecl.id.name);
        typeDecl.id.object = scopeObject;
        currentScope.insert(scopeObject);
    }

    public void declareFunc(FuncDecl funcDecl, ScopeObject.KIND kind)
    {
        ScopeObject scopeObject = new ScopeObject(funcDecl, kind, funcDecl.name.name);
        funcDecl.name.object = scopeObject;
        rootScope.insert(scopeObject);
    }

    public Id requestId()
    {
        Id id = new Id();
        id.name = String.valueOf(embeddedId);
        return id;
    }

    public void printTree() throws IOException
    {
        FileOutputStream fileOutputStream = new FileOutputStream("ast.txt");
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "utf-8");
        outputStreamWriter.write("package name: " + packageName + "\n");
        outputStreamWriter.write("Imports:\n");
        for(Import _import : imports)
        {
            for(String pathSpec : _import.pathSpec)
                outputStreamWriter.write("\t" + pathSpec + "\n");
            if(_import.alias != null)
                outputStreamWriter.write("\talias: " + _import.alias.name + "\n");
        }

        for(Decl decl : decls)
        {
            printAst.printDecl(decl, 0, outputStreamWriter);
        }
        outputStreamWriter.flush();
        outputStreamWriter.close();
        fileOutputStream.close();
    }

    public List<Id> resolveImports()
    {

        Map<String, Boolean> importName = new HashMap<>();
        for(Import _import : imports)
        {
            for(String path : _import.pathSpec)
            {
                path = path.substring(1, path.length()-1);
                int index = path.lastIndexOf('/');
                importName.put(path.substring(index+1), true);
            }
        }

        List<Id> packageObjects = new ArrayList<>();
        List<Id> unresolve_tmp = new ArrayList<>() ;
        for(Id id : unresolve)
        {
            if(importName.containsKey(id.name))
            {
                id.object = new ScopeObject(null, ScopeObject.KIND.Pkg, id.name);
                packageObjects.add(id);
            }
            else
                unresolve_tmp.add(id);
        }
        unresolve = unresolve_tmp;
        return packageObjects;
    }
}
